﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Blizzard.Contracts;

namespace Blizzard.Container
{
   static class RelationInstanceContainer
    {

       
       private static Dictionary<Key, RelationInstance> innerContainer = new Dictionary<Key, RelationInstance>();

       internal static void ClearContainer()
       {
           innerContainer.Clear();
       }

       public static void Add(RelationInstance Ri)
       {
           if (HasRi(Ri))
           {//When we are here, it means that there is an relationInstance for these two objects
               //(objects involved in Ri). so a check should be done to see whether the status
               //of the incomming Ri is the same as the already existing one.

               //One senario is that a relation has been broken but later is re established
               // in that case the incomming Ri has the status new and existing Ri has the
               // status broken. in that case we make status None for the existing Ri and
               // let it be that way in container (so we dont add the new one). and the status
               // None means that no update has to be done in to database. 
               //For Other senarios at this moment we throw an exception because we dont know at this
               //moment whether they are possible senarios in real. so the exception makes sure 
               // that they dont go unnotice. we fix them when if an actual senario happens.
     
               
               RelationInstance existingRi = RelationInstanceContainer.GetRi(Ri.Obj1, Ri.Obj2);
               if (Ri.Status == RelationStatus.New && existingRi.Status == RelationStatus.Broken)
               {
                   existingRi.Status = RelationStatus.None;
               }
               else
               {
                   throw new ApplicationException("Two objects have already a RelationInstance in the container");
               }
 
           
           }
           else
           {
               Key key = new Key(Ri.Obj1, Ri.Obj2);
               innerContainer.Add(key, Ri);
           }
       }

     
       public static bool HasRi(RelationInstance Ri)
       {
           return HasRi(Ri.Obj1, Ri.Obj2);
       }

       /// <summary>
       /// Returns true if there is a RelationInstance object in the container
       /// based on two objects (obj1 and obj2). Also if one of those objects
       /// is has a child role and already has a parent this method returns true.
       /// (that means that in this case it is not nessecary that the combination is unique)
       /// </summary>
       /// <param name="obj1"></param>
       /// <param name="obj2"></param>
       /// <returns></returns>
       public static bool HasRi(IDataObject obj1, IDataObject obj2)
       {

           bool hasri = false;

           
           Key key12 = new Key(obj1, obj2);
           Key key21 = new Key(obj2, obj1);

           if (innerContainer.ContainsKey(key12))
           {
               hasri = true;
           }
           else
           {
               if (innerContainer.ContainsKey(key21))
               {
                   hasri = true;
               }
           }


           return hasri;
       }

       /// <summary>
       /// this method indicate constraint on oneToMany relationship; 
       /// when object obj is of type child (in a one to many relation)
       /// it can only have one relationInstance with the corresponding parent type
       /// </summary>
       /// <param name="obj"></param>
       /// <returns></returns>
       public static bool HasAlreadyOneParent(IDataObject childObj , Type parentType)
       {
           bool retType = false;

           BList<IDataObject> list = GetAssociates(childObj, parentType);

           if (list.Count != 0)
           {
               retType = true;
           }

           return retType;
       }

       public static RelationInstance GetRi(IDataObject obj1, IDataObject obj2)
       {
           RelationInstance retRi = null;

           Key key12 = new Key(obj1, obj2);
           Key key21 = new Key(obj2, obj1);

           if (innerContainer.ContainsKey(key12))
           {
               retRi = innerContainer[key12]; 
           }
           else
           {
               if (innerContainer.ContainsKey(key21))
               {
                   retRi = innerContainer[key21]; 
               }
           }


           return retRi;
       }

       //public static List<RelationInstance> FindParent(IDataObject mainObject, Type AssociatedType)
       //{
       //    List<RelationInstance> retList = new List<RelationInstance>();

       //    List<IDataObject> AssociatedObjecList = GetDataObjects(mainObject, AssociatedType);

       //    foreach (IDataObject associatedObj in AssociatedObjecList)
       //    {

       //        RelationInstance ri = GetRi(mainObject, associatedObj);
       //        retList.Add(ri);

       //    }

       //    return retList;
       //}


       public static void BreakTheRelationShip(RelationInstance ri)
       {
          ri.Status = RelationStatus.Broken;
       }
       public static void BreakTheRelationShip(IDataObject obj1, IDataObject obj2)
       {
           RelationInstance ri = GetRi(obj1, obj2);
           ri.Status = RelationStatus.Broken;
       }

       public static void RemoveFromContainer(RelationInstance ri)
       {
           RemoveFromContainer(ri.Obj1, ri.Obj2);
       }
       public static void RemoveFromContainer(IDataObject obj1, IDataObject obj2)
       {
           Key key12 = new Key(obj1, obj2);
           Key key21 = new Key(obj2, obj1);

           if (innerContainer.ContainsKey(key12))
           {
               innerContainer.Remove(key12);
           }
           else
           {
               if (innerContainer.ContainsKey(key21))
               {
                   innerContainer.Remove(key21);
               }
           }
       }

       /// <summary>
       /// Checks the container for a relationInstance of these two objects (obj1 and obj2)
       /// if it does not exist, it creates one. But note, this does not mean that the
       /// created relationInstance should have a New status. you just create an relationInstance
       /// object (which can be based on an already persisted relation in database)
       /// however, when you say a relation has new status that means that it is not comming from
       /// db but it is a real new relation which should be persisted.
       /// setting the status new for a relation should not happen here.
       /// </summary>
       /// <param name="obj1"></param>
       /// <param name="obj2"></param>
       public static void GetOrCreateRelationShip(IDataObject obj1, IDataObject obj2)
       {

           Type type1 = obj1.GetType();
           Type type2 = obj2.GetType();

           //First retrieving an already made RelationDefinition object for these two types
           RelationDefinition rd = RelationDefinitionContainer.GetRd(type1, type2);

           RelationInstance Ri = new RelationInstance(rd, obj1, obj2);

           if (!RelationInstanceContainer.HasRi(Ri))
               RelationInstanceContainer.Add(Ri);

       }
       /// <summary>
       /// use this method when you make a new relationship which should be
       /// persisted in db (so it is not based on an already existing relation)
       /// </summary>
       /// <param name="obj1"></param>
       /// <param name="obj2"></param>
       public static void CreateNewRelationShip(IDataObject obj1, IDataObject obj2)
       {

           Type type1 = obj1.GetType();
           Type type2 = obj2.GetType();

           //First retrieving an already made RelationDefinition object for these two types
           RelationDefinition rd = RelationDefinitionContainer.GetRd(type1, type2);

           RelationInstance Ri = new RelationInstance(rd, obj1, obj2);
           Ri.Status = RelationStatus.New;

           RelationInstanceContainer.Add(Ri);

       }


       /// <summary>
       /// Gets a list of
       /// </summary>
       /// <param name="mainObject"></param>
       /// <param name="AssociatedType"></param>
       /// <returns></returns>
       public static BList<TAssociate> GetAssociates<TAssociate>(IDataObject mainObject)
       {
           BList<TAssociate> retList = new BList<TAssociate>(mainObject);

           foreach (RelationInstance ri in innerContainer.Values)
           {
               if (!(ri.Status == RelationStatus.Broken))
               {//when here we are sure that the realtion is not broken.
                   if (ri.Obj1 == mainObject)
                   {
                       if (ri.Obj2.GetType() == typeof(TAssociate))
                       {
                           retList.InternalAdd((TAssociate)ri.Obj2);
                       }
                   }
                   else if (ri.Obj2 == mainObject)
                   {
                       if (ri.Obj1.GetType() == typeof(TAssociate))
                       {
                           retList.InternalAdd((TAssociate)ri.Obj1);
                       }
                   }
               }
           }


           return retList;
       }

       public static BList<IDataObject> GetAssociates(IDataObject mainObject, Type AssociateType)
       {
           BList<IDataObject> retList = new BList<IDataObject>(mainObject);

           foreach (RelationInstance ri in innerContainer.Values)
           {
               if (!(ri.Status == RelationStatus.Broken))
               {//when here we are sure that the realtion is not broken.
                   if (ri.Obj1 == mainObject)
                   {
                       if (ri.Obj2.GetType() == AssociateType)
                       {
                           retList.InternalAdd(ri.Obj2);
                       }
                   }
                   else if (ri.Obj2 == mainObject)
                   {
                       if (ri.Obj1.GetType() == AssociateType)
                       {
                           retList.InternalAdd(ri.Obj1);
                       }
                   }
               }
           }


           return retList;
       }

       /// <summary>
       /// Gets all existing relations in the container with no exeption.
       /// </summary>
       /// <returns></returns>
       public static List<RelationInstance> GetAllRelations()
       {

           List<RelationInstance> list = new List<RelationInstance>();

           foreach (RelationInstance ri in innerContainer.Values)
           {

               list.Add(ri);
           }

           return list;

       }

       /// <summary>
       /// Get all relationInstances regarding to the mainObject
       /// </summary>
       /// <param name="mainObject"></param>
       /// <returns></returns>
       public static List<RelationInstance> GetAllRelations(IDataObject mainObject)
       {

           List<RelationInstance> list = new List<RelationInstance>();

           foreach (RelationInstance ri in innerContainer.Values)
           {

               if (ri.Obj1 == mainObject || ri.Obj2 == mainObject)
               {
                   list.Add(ri);
               }
             
           }

           return list;

       }

       /// <summary>
       /// Gets all the RelationInstances of the main object with regard to relatedType
       /// </summary>
       /// <param name="mainObject"></param>
       /// <param name="relatedType"></param>
       /// <returns></returns>
       public static List<RelationInstance> GetAllRelations(IDataObject mainObject, Type relatedType)
       {
           List<RelationInstance> list = new List<RelationInstance>();

           foreach (RelationInstance ri in innerContainer.Values)
           {

               if (ri.Obj1 == mainObject)
               {
                   if (ri.Obj2.GetType() == relatedType)
                   {
                       list.Add(ri);
                   }
               }
               else if (ri.Obj2 == mainObject)
               {
                   if (ri.Obj1.GetType() == relatedType)
                   {
                       list.Add(ri);
                   }
               }

           }

           return list;
       }

       private struct Key
       {
           private IDataObject _obj1;
           private IDataObject _obj2;

           public Key(IDataObject obj1, IDataObject obj2)
           {
               _obj1 = obj1;
               _obj2 = obj2;
           }

           public IDataObject Obj1 { get { return _obj1; } }
           public IDataObject Obj2 { get { return _obj2; } }
       }


    }

   
}
